/*
 * travel.c
 * This file is part of lab9
 *
 * Copyright (C) 2012 - Luke William Westby & Jon Gautsch
 *
 * lab9 is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * lab9 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with lab9. If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#define MAX_LINE 1028

/* -------------------------------------------------------------------------- *
 * flight structure
 * store flight data in an abstracted context for easier manipulation
 * -------------------------------------------------------------------------- */

typedef struct _flight {
	int id;					// flight number
	char origin[4];			// leaving from this airport
	char dest[4];			// arriving here
	int price;				// cost of flight
	int depart;				// time leaving origin
	int arrive;				// time arriving at dest
} flight;

/* -------------------------------------------------------------------------- *
 * err
 * small function for printing debug information in case of a memory error
 * -------------------------------------------------------------------------- */

void err(const char *message, int line, char *file) {
	fprintf(stderr, "%s -- %s:%d\n\n", message, file, line);
	exit(1);
}

/* -------------------------------------------------------------------------- *
 * get_master
 * open the file named by string filename and extract data within into a flight
 * array. store the number of flights read in the integer pointed by nflights
 * and return the generated master list as a multi-element flight *.
 * -------------------------------------------------------------------------- */

flight *travel_get_master(const char *filename, int *nflights) {
    
    FILE *infile;
    
    infile = fopen("flights.txt", "r");
    
    if(!infile) err("Could not open flights.txt", __LINE__, __FILE__);
    
	// declare some arrays
    flight *flights = NULL;
    flight *new_flights = NULL;
    
	// set up necessary data storage for line-by-line parsing
    char line[MAX_LINE];					// line
    int arr_min, arr_hr, dpt_min, dpt_hr;	// parse time to minutes
    int nfl = 0;							// number of flights
    int flights_size = 0;
    
    // pointers to internal data on current flight being parsed
    char *cur_ori, *cur_dest;
    int *cur_prc, *cur_arr, *cur_dpt, *cur_id;
    cur_ori = cur_dest = cur_prc = cur_arr = cur_dpt = cur_id = NULL;
    
    while(fgets(line, MAX_LINE, infile) != NULL) {
        
        // expand array, error check
    	flights = realloc(flights, flights_size + sizeof(flight));
        flights_size += sizeof(flight);
        
        // set some pointers so I don't have to keep using struct notation
        cur_ori  = flights[nfl].origin;
        cur_dest = flights[nfl].dest;
        cur_prc  = &flights[nfl].price;
        cur_dpt  = &flights[nfl].depart;
        cur_arr  = &flights[nfl].arrive;
		cur_id   = &flights[nfl].id;
		
        // parse line
        sscanf(line, "%s %s %d:%d %d:%d $%d", cur_ori, cur_dest, &dpt_hr,
               &dpt_min, &arr_hr, &arr_min, cur_prc);
		
		// set actual minute values
        *cur_dpt = dpt_hr * 60 + dpt_min;
        *cur_arr = arr_hr * 60 + arr_min;
        
        *cur_id = nfl;
        
        // increment
        nfl++;
    }
    
    fclose(infile);
    
    // output
    *nflights = nfl;
    return flights;
}


/* -------------------------------------------------------------------------- *
 * print_list
 * take the mask choices and print out the flights selected in it with the
 * format specified in the printf statement
 * -------------------------------------------------------------------------- */
void travel_print_list(flight *list, int nflights, uint8_t *choices) {
    
    // declare
	char *cur_ori, *cur_dest;
    int cur_prc, cur_arr, cur_dpt, cur_id, i, arr_hr, arr_min, dpt_hr, dpt_min;
    
    // init
    cur_ori = cur_dest = NULL;
    
    // loop through all flights
    for(i = 0; i < nflights; i++) {
    	
    	// only print flights allowed by mask
    	if(choices[i] == 1) {
            
            // set pointers
			cur_ori  = list[i].origin;
			cur_dest = list[i].dest;
			cur_prc  = list[i].price;
			cur_dpt  = list[i].depart;
			cur_arr  = list[i].arrive;
			cur_id   = list[i].id;
            
            // calculate integers for time values
			arr_hr  = cur_arr/60;
			arr_min = cur_arr % 60;
			dpt_hr  = cur_dpt/60;
			dpt_min = cur_dpt % 60;
            
            // print
			printf("Flight #%3d  --  %3s  %3s  %2d:%.2d  %2d:%.2d  $%3d\n", cur_id, cur_ori,
                   cur_dest, dpt_hr, dpt_min, arr_hr, arr_min, cur_prc);
            
		}
	}
}


/* -------------------------------------------------------------------------- *
 * print_itinerary
 * print the itinerary to the terminal by scanning through user history
 * -------------------------------------------------------------------------- */

void print_itinerary(flight *list, int nflights, uint8_t *choices, int8_t hist[100]){
    
    // declare
    char *cur_ori, *cur_dest;
    int cur_prc, cur_arr, cur_dpt, cur_id, i, arr_hr, arr_min, dpt_hr, dpt_min, j;
   	
   	// init 
    cur_ori = cur_dest = NULL;
    
    // scan for nflights
    for(i = 0; i < nflights; i++) {
    	
    	// get flight number from history
        j = hist[i];
    	if(choices[j] == 1) {
            
            // set pointers
			cur_ori  = list[j].origin;
			cur_dest = list[j].dest;
			cur_prc  = list[j].price;
			cur_dpt  = list[j].depart;
			cur_arr  = list[j].arrive;
			cur_id   = list[j].id;
            
            // calculate time
			arr_hr  = cur_arr/60;
			arr_min = cur_arr % 60;
			dpt_hr  = cur_dpt/60;
			dpt_min = cur_dpt % 60;
            
            // print
			printf("Flight #%3d  --  %3s  %3s  %2d:%.2d  %2d:%.2d  $%3d\n", cur_id, cur_ori,
                   cur_dest, dpt_hr, dpt_min, arr_hr, arr_min, cur_prc);
			
		}
	}
}

/* ------------------------------------------------------------------------- *
 * num_avail_flights
 * returns number of available flights based on already selected flights
 * ------------------------------------------------------------------------- */

int num_avail_flights(flight *list, int nflights, uint8_t *choices){
    char *cur_ori, *cur_dest;
    int cur_prc, cur_arr, cur_dpt, cur_id, i, arr_hr, arr_min, dpt_hr, dpt_min, num_avail = 0;
    cur_ori = cur_dest = NULL;
    
    for(i = 0; i < nflights; i++) {
    	
    	if(choices[i] == 1) {
            
			cur_ori  = list[i].origin;
			cur_dest = list[i].dest;
			cur_prc  = list[i].price;
			cur_dpt  = list[i].depart;
			cur_arr  = list[i].arrive;
			cur_id   = list[i].id;
            
			arr_hr  = cur_arr/60;
			arr_min = cur_arr % 60;
			dpt_hr  = cur_dpt/60;
			dpt_min = cur_dpt % 60;
            
			num_avail++;
            
		}
	}
    return num_avail;
}

/* ------------------------------------------------------------------------- *
 * num_flights_in_itinerary
 * returns the number of flights in the itinerary list 
 * ------------------------------------------------------------------------- */

int num_flights_in_itinerary(flight *list, int nflights, uint8_t *choices, int8_t hist[100]){
    char *cur_ori, *cur_dest;
    int cur_prc, cur_arr, cur_dpt, cur_id, i, arr_hr, arr_min, dpt_hr, dpt_min, j, num_itin_flights = 0;
    cur_ori = cur_dest = NULL;
    
    for(i = 0; i < nflights; i++) {
    	
        j = hist[i];
    	if(choices[j] == 1) {
            
			cur_ori  = list[j].origin;
			cur_dest = list[j].dest;
			cur_prc  = list[j].price;
			cur_dpt  = list[j].depart;
			cur_arr  = list[j].arrive;
			cur_id   = list[j].id;
            
			arr_hr  = cur_arr/60;
			arr_min = cur_arr % 60;
			dpt_hr  = cur_dpt/60;
			dpt_min = cur_dpt % 60;
            
			num_itin_flights++;
			
		}
	}
    return num_itin_flights;
}

/* -------------------------------------------------------------------------- *
 * get_origin
 * update the currently available flights masked in avail according to the 
 * airport code in ori and the minimum departure time
 * -------------------------------------------------------------------------- */

void travel_get_origin(flight *list, int nflights, uint8_t *avail, char *ori, int min_time) {
	
	// declare, init
	int i, cur_dpt;
	char *cur_ori = NULL;
	
	// loop through flights
	for(i = 0; i < nflights; i++) {
		
		// get origin and time
		cur_ori = list[i].origin;
		cur_dpt = list[i].depart;
		
		// compare and set mask
		if(strcmp(cur_ori, ori) == 0 && cur_dpt > min_time) avail[i] = 1;
		else avail[i] = 0;
	}
}


/* -------------------------------------------------------------------------- *
 * add_flight
 * add a flight specified by flight number choice to itinerary mask and update
 * available flights masked by avail accordingly
 * -------------------------------------------------------------------------- */
 
int travel_add_flight(flight *list,
                      int nflights,
                      uint8_t *avail,
                      uint8_t *itin,
                      int choice,
                      int *min_time,
                      int8_t *hist,
                      int *nhist) {
	
	// declare, init
	char *cur_ori = NULL;
	int cur_dpt;
    
	// check that the flight is available
	if(avail[choice] == 0) {
		//puts("\nThat flight number is not available to you.");
		return -1;
	}
	
	// time is valid, add the flight to the itinerary
	itin[choice] = 1;
	avail[choice] = 0;
	
	// update the history
	hist[*nhist] = choice;
	(*nhist) += 1;
	
	// reset the minimum time
	*min_time = list[choice].arrive;
	
	// update the available flights
	cur_ori = list[choice].dest;
	travel_get_origin(list, nflights, avail, cur_ori, *min_time);
	
    return 0;
}

/* -------------------------------------------------------------------------- *
 * remove_flight
 * if there is only one flight in the history, just zero out everything, else
 * roll back the available flights and itinerary masks by one and push history
 * back accordingly
 * -------------------------------------------------------------------------- */
 
void travel_remove_flight(flight *list,
						  int nflights,
						  uint8_t *avail,
						  uint8_t *itin,
						  int *min_time,
						  int8_t *hist,
						  int *nhist) {
	
	// only one flight in history, just obliterate all data
	if(*nhist == 1) {
		memset(avail, 1, nflights);
		memset(itin, 0, nflights);
		memset(hist, -1, nflights);
		*nhist = 0;
		
	// roll back history and availability by 1
	} else {
		int prev_1 = hist[*nhist-1]; 			// for origin
		int prev_2 = hist[*nhist-2]; 			// for flight time
		itin[prev_1] = 0;			 			// delete from itin
		(*nhist)--;					 			// roll back history
		char *cur_ori = list[prev_1].origin;	// set origin
		*min_time = list[prev_2].arrive;		// set time
		travel_get_origin(list, nflights, avail, cur_ori, *min_time); // avail
	}
}

/* -------------------------------------------------------------------------- *
 * export_itin
 * print itinerary to a text file named "filename"
 * -------------------------------------------------------------------------- */
 
void travel_export_itin(flight *list, int nflights, uint8_t *itin, const char *filename, int8_t hist[100]) {
	
	// get file
	FILE *outfile = fopen(filename, "w");
	if(!outfile) err("Could not open text file to print itinerary", __LINE__, __FILE__);
	
	// print header
	fputs("Flight # -- Origin - Destination - Departure Time - Arrival Time - Cost\n", outfile);
	
	// init and declare
	char *cur_ori, *cur_dest;
    int cur_prc, cur_arr, cur_dpt, cur_id, i, arr_hr, arr_min, dpt_hr, dpt_min, j, total_cost = 0;
    cur_ori = cur_dest = NULL;
    
    // loop through flights
    for(i = 0; i < nflights; i++) {
    	
    	// get flight number from history
        j = hist[i];
    	if(itin[j] == 1) {
            
            // set pointers
			cur_ori  = list[j].origin;
			cur_dest = list[j].dest;
			cur_prc  = list[j].price;
			cur_dpt  = list[j].depart;
			cur_arr  = list[j].arrive;
			cur_id   = list[j].id;
            
            // calculate time
			arr_hr  = cur_arr/60;
			arr_min = cur_arr % 60;
			dpt_hr  = cur_dpt/60;
			dpt_min = cur_dpt % 60;
            
            // write to file
			fprintf(outfile, "#%d -- %s %s %d:%.2d %d:%.2d $%d\n", cur_id, cur_ori,
					cur_dest, dpt_hr, dpt_min, arr_hr, arr_min, cur_prc);
			
			// increment cost
			total_cost += list[j].price;
		}
	}
	
	// print cost	
	fprintf(outfile, "\nThe total cost of your trip will be: $%d\n", total_cost);
    
    // close file
	fclose(outfile);
}


/* ------------------------------------------------------------------------- *
 * enter_origin_and_update_flightlist_accordingly
 * User enters origin airport code and flight list updates accordingly 
 * ------------------------------------------------------------------------- */

void enter_origin_and_update_flightlist_accordingly(flight *master_list, int nflights, uint8_t *avail, char *airport, int min_time, char origin[3]){
    
    int i;
    
    printf("\nPlease enter the 3 letter code of the airport from which you'd like to depart: ");
    scanf("%s", origin);
    
    // if user entered "back" already, tell them that doesn't work
    
    while (strcmp(origin, "back") == 0){
        printf("\nThere are no flights to remove from the itinerary, so \"back\" is an invalid command.\n");
        printf("Please enter a valid 3 letter code for your origin airport: ");
        scanf("%s", origin);
    }
    
    // check to make sure the user entered code is 3 letters
    
    while (strlen(origin) != 3) {
        if (strlen(origin) > 3) printf("The airport code you entered is too long. The code must be 3 letters.\n");
        else printf("The airport code you entered is too short. The code must be 3 letters.\n");
        printf("Please re-enter the 3 letter code for your origin airport: ");
        scanf("%s", origin);
    }
    
    // if user entered "end", go ahead and end the program. No vacation for them.
    
    if (strcmp(origin, "end") == 0) {
        printf("\nYou have chosen to end the program without creating an itinerary. Have a nice day.\n");
        exit(0);
    }
    while (strcmp(origin, "back") == 0){
        printf("\nThere are no flights to remove from the itinerary, so \"back\" is an invalid command.\n");
        printf("Please enter a valid 3 letter code for your origin airport: ");
        scanf("%s", origin);
    }
    
    // check to make sure the user entered an airport code that exists in the flight list
    
    int flight_exists = 0;
    
    while (!flight_exists) {
        
        // make sure the user entered origin is all caps
        for (i = 0; i < strlen(origin); i++) {
            origin[i] = toupper(origin[i]);
        }
        
        // compare the user entered origin string to those that exist in the master flight list
        for (i = 0; i < nflights; i++) {
            if (!strcmp(master_list[i].origin, origin)){
                flight_exists = 1;
                break;
            }
        }
        
        // if the user entered origin code doesn't exist, get a new airport code from them
        if (!flight_exists) {
            printf("The airport corresponding to the code you entered does not exist on the flight list.\n");
            printf("Please re-enter the 3 letter airport code for your origin airport: ");
            scanf("%s", origin);
        }
    }
    
    
    // Use user enter origin value to get the flights that originate at the user's desired airport
    
	strcpy(airport, origin);
	travel_get_origin(master_list, nflights, avail, airport, min_time);
	printf("\nCurrently available flights from %s:\n", origin);
	travel_print_list(master_list, nflights, avail);
	puts("");
    
}

int main() {
    
    /* declare the variables we will need --------------------------------------- */
    
	flight *master_list = NULL;	// master list to be read from file
	int nflights, min_time, i;	// number of flights, arrival of last flight
	uint8_t *itin, *avail;		// nflights-element arrays, 0 or 1
	int8_t hist[100];			// undo history
	int cur_hist;				// number of items in history
	char airport[4];			// storage for an aiport code
    
    /* read in flights to master_list ------------------------------------------- */
	
	master_list = travel_get_master("flights.txt", &nflights);
	
    /* initialize flight lists -------------------------------------------------- */
	
	// don't know maximum number of possible flights so assume it is all of them
	itin = (uint8_t *)malloc(nflights);
	// set itinerary values to all 0
	memset(itin, 0, nflights);
	
	// maximum number of available flights actually is all of them
	avail = (uint8_t *)malloc(nflights);
	// set all to 1
	memset(avail, 1, nflights);
	
	memset(hist, -1, nflights);
	
	min_time = 0;
	cur_hist = 0;
    
    /* print initial available flights list ------------------------------------- */
    
	travel_print_list(master_list, nflights, avail);
    
    /*--------------------------------------------------------------------------- */
    /* user interaction --------------------------------------------------------- */
    /*--------------------------------------------------------------------------- */
    
    
    printf("\n--------------------------------------------------\n");
    printf("Welcome to Luke and Jon's travel itinerary program!");
    printf("\n--------------------------------------------------\n");
    printf("User Instructions:\n\n");
    printf("    - To end the program and export your selected flights to \"itinerary.txt\", simply enter \"end\"\n");
    printf("    - To remove the most recently added flight from the itinerary, enter \"back\"\n");
    printf("\n    > The master flight list has been shown above.\n");
    printf("    > You will start by entering the airport code corresponding to your trip origin.\n");
    printf("    > After that, you will add flights to your itinerary by entering the flight number ");
    printf("\n      (the available flights will be shown to you each time)\n\n");
    printf("~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~\n");
    
    char origin[1028];   // what will be the origin string, used in this next function
    
    /* User enters origin, and a flight list originating at the airport is generated and presented */
    
    enter_origin_and_update_flightlist_accordingly(master_list, nflights, avail, airport, min_time, origin);
    
    int flight_num; // used as the flight number entered by the user in order to select a flight
    
    char flight_action[1028]; // this is the string user enters stuff into. If they enter either "end" or "back"
    // then the appropriate things happen. Anything else is tried to be converted to
    // an int, and if the value is invalid (not a number, or in the wrong range), the
    // user will have to re-enter
    
    
    /* main event loop ----------------------------------------------------------- */
    
    while (1) {
        printf("\n ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ \n\n");
    loop: // point which is returned to when either an invalid command or number is attempted by the user
        printf("Enter the flight number you'd like to take next from the displayed list of flights: ");
        scanf("%s", flight_action);
        
        // if "end" is entered by the user, the program exports, frees, and exits
        if (strcmp(flight_action,"end") == 0) {
            
            printf("\nYou have chosen to finalize your itinerary.\n");
        end_and_export:
            printf("Now exporting your itinerary to \"itinerary.txt\"\n");
            travel_export_itin(master_list, nflights, itin, "itinerary.txt", hist);
            printf("\nYour final itinerary is as follows: \n");
            print_itinerary(master_list, nflights, itin, hist);
            
            if(master_list) free(master_list);
            if(itin) free(itin);
            if(avail) free(avail);
            
            return 0;
            
            // else, if the user enters back, the program checks to see if it can go back
        } else if (strcmp(flight_action,"back") == 0){
            
            // if it can't go back (because there are no flights in the itinerary),
            // then the command was invalid, and program returns to loop:
            if (num_flights_in_itinerary(master_list, nflights, itin, hist) == 0) {
                printf("There are no flights in the itinerary, so you can't cancel the most recent flight with \"back\"\n");
                goto loop;
            }
            
            // otherwise if it can go back, it removes the most recent flight on the itinerary
            travel_remove_flight(master_list, nflights, avail, itin, &min_time, hist, &cur_hist);
            puts("\nItinerary:");
            print_itinerary(master_list, nflights, itin, hist);
            puts("\nCurrently available flights:");
            travel_print_list(master_list, nflights, avail);
            puts("\nValues currently stored in the history:");
            for(i = 0; i < cur_hist; i++) printf("%d\n", hist[i]);
            
            // else, we try to get a number from what the user entered, and see if it's a flight # we can add to the itinerary
        } else {
            flight_num = atoi(flight_action); // pulls the number from the string
            // we use a string so the user could enter either "end" or "back"
            
            // this will attempt to add the flight # to the itinerary, if it's valid and works with the times and
            // locations it will work. Otherwise it will tell me why it didn't work, and either return to loop:, or
            // if there are no more available flights based on my travel plan, it will jump to end_and_export:
            if (travel_add_flight(master_list, nflights, avail, itin, flight_num, &min_time, hist, &cur_hist) == -1) {
                printf("Invalid flight number\n");
                goto loop;
            }
            if (num_avail_flights(master_list, nflights, avail) == 0) {
                printf("\nThere are no more flights available for your travels.\n");
                goto end_and_export;
            }
        }
        
        // informs user of the flights currently in the itinerary before looping again
        puts("\nCurrent Itinerary:");
        print_itinerary(master_list, nflights, itin, hist);
        
        // informs user of flights he/she could choose to add to the itinerary next
        printf("\nCurrently available flights after adding flight %d to itinerary:\n", flight_num);
        travel_print_list(master_list, nflights, avail);
    }
    
    return 0;
}
